package easycmd

import (
	"errors"
	"flag"
	"fmt"
	"gitee.com/hongzhaomin/hzm-common-go/strutil"
	"gitee.com/hongzhaomin/hzm-common-go/toolkit"
	"os"
	"reflect"
)

const (
	// 解析默认值
	initValTag = "initVal"
	// 解析参数用途描述
	usageTag = "usage"
	// 命令行参数名
	argNameTag = "argName"
)

// Command 命令行程序的命令对象
// 它提供命令的初始化，解析，将命令行输入值绑定到处理器属性值等功能
// 也支持自定义注册单个命令行参数
type Command struct {
	name      string         // 命令名称
	desc      string         // 命令描述
	f         *flag.FlagSet  // flagSet
	parsedMap map[string]any // 解析后的参数值
	handler   Handler        // 命令处理器
}

// 处理器字段自动解析注册命令行参数
func (c *Command) addParams() {
	rv := reflect.ValueOf(c.handler)
	if rv.Kind() != reflect.Ptr {
		fmt.Println("subcommands init params err: must be ptr")
		os.Exit(1)
	}
	rv = reflect.Indirect(rv)
	if rv.Kind() != reflect.Struct {
		fmt.Println("subcommands init params err: must be struct")
		os.Exit(1)
	}

	rt := rv.Type()
	for i := 0; i < rt.NumField(); i++ {
		field := rt.Field(i)
		if !field.IsExported() {
			// 字段不可导出
			continue
		}
		if _, ok := field.Tag.Lookup(usageTag); !ok {
			// tag不存在usage，表示该字段不是命令参数
			continue
		}

		rtField := field.Type
		if rtField.Kind() == reflect.Ptr {
			rtField = rtField.Elem()
		}

		// 解析定义的命令行参数名
		argName := c.resolveArgName(field)

		rvField := rv.Field(i)
		if rtField.Kind() == reflect.Bool ||
			rtField.Kind() == reflect.String ||
			rtField.Kind() == reflect.Int ||
			rtField.Kind() == reflect.Int64 ||
			rtField.Kind() == reflect.Uint ||
			rtField.Kind() == reflect.Uint64 ||
			rtField.Kind() == reflect.Float64 {
			var val any
			if initVal, ok := field.Tag.Lookup(initValTag); ok {
				rvInitVal, err := toolkit.ReflectConvert4Str(rtField, initVal)
				if err != nil {
					panic(errors.New("类型转换异常" + err.Error()))
				}
				val = rvInitVal.Elem().Interface()
			} else {
				val = reflect.Indirect(rvField).Interface()
			}

			usage := field.Tag.Get(usageTag)
			c.AddParam(argName, val, usage)
		}
	}
}

func (c *Command) resolveArgName(field reflect.StructField) string {
	// 解析定义的命令行参数名
	argName, ok := field.Tag.Lookup(argNameTag)
	if !ok {
		// 未定义，则默认将字段名称首字母小写的驼峰式名称作为命令行参数名
		argName = strutil.FirstLetter2Lower(field.Name)
	}
	return argName
}

// AddParam 手动注册单个命令行参数
func (c *Command) AddParam(name string, value any, usage string) *Command {
	var p any
	switch val := value.(type) {
	case int:
		p = c.f.Int(name, val, usage)
	case int64:
		p = c.f.Int64(name, val, usage)
	case uint:
		p = c.f.Uint(name, val, usage)
	case uint64:
		p = c.f.Uint64(name, val, usage)
	case float64:
		p = c.f.Float64(name, val, usage)
	case string:
		p = c.f.String(name, val, usage)
	case bool:
		p = c.f.Bool(name, val, usage)
	default:
		panic(errors.New("不支持的参数类型"))
	}
	c.parsedMap[name] = p
	return c
}

// BindHandler 绑定命令处理器
func (c *Command) BindHandler(handler Handler) *Command {
	if handler == nil {
		panic("command handler must be not nil")
	}

	c.handler = handler

	rvHandler := reflect.ValueOf(handler)
	if rvHandler.Kind() != reflect.Ptr {
		rvNewHandler := reflect.New(rvHandler.Type())
		rvNewHandler.Elem().Set(rvHandler)
		c.handler = rvNewHandler.Interface().(Handler)
	}
	c.addParams()

	return c
}

// execute 命令执行入口
func (c *Command) execute() {
	err := c.f.Parse(os.Args[2:])
	if err != nil {
		fmt.Println(fmt.Sprintf("'%s' command parse args err:", c.name), err)
		os.Exit(1)
	}

	// 将解析出来的值绑定到handler上
	rv := reflect.Indirect(reflect.ValueOf(c.handler))
	rt := rv.Type()
	for i := 0; i < rt.NumField(); i++ {
		field := rt.Field(i)
		argName := c.resolveArgName(field)
		if valPtr, exist := c.parsedMap[argName]; exist {
			if field.Type.Kind() == reflect.Ptr {
				rv.Field(i).Set(reflect.ValueOf(valPtr))
			} else {
				rv.Field(i).Set(reflect.ValueOf(valPtr).Elem())
			}
		}
	}

	c.handler.DoHandle(c)
}

// Regis 命令注册
func (c *Command) Regis() {
	if c.name == "" {
		panic("command name must be not blank")
	}
	if c.desc == "" {
		panic("command usage must be not blank")
	}
	if c.handler == nil {
		panic("command handler must be not nil")
	}
	ctx.add(c)
}

// GetArgVal 根据命令行参数名获取值
func (c *Command) GetArgVal(argName string) any {
	return c.parsedMap[argName]
}
